Kriging.f90 Source File

Routines to implement Kriging interpolation



Source Code

!! Routines to implement Kriging interpolation
!|author:  <a href="mailto:giovanni.ravazzani@polimi.it">Giovanni Ravazzani</a>
! license: <a href="http://www.gnu.org/licenses/">GPL</a>
!    
!### History
!   
! current version  1.0 - 1st July 2018   
!
! | version  |  date       |  comment |
! |----------|-------------|----------|
! | 1.0      | 01/Jul/2018 | Original code |
!
!### License  
! license: GNU GPL <http://www.gnu.org/licenses/>
!
!### Module Description 
!   routines to implement Kriging interpolation. Firstly the 
!   semivariogram is generated from a given sample. 
!   The sample point pairs are ordered into even-width bin,
!   separated by the euclidean distance of the point pairs.
!   The Semivariance in the bin is calculated by the Matheron estimator.
!   If number of lags and lag max distance are not given
!   they are automatically computed or set to default value.    
!   Empirical semivariogram is then automatically fitted
!   to the best fitting model among Spherical, Exponential,
!   Gaussian, and Power, considering the nugget effect.
!   Fitting algorithm is adapted from the VARFIT model 
!   by Pardo-Iguzquiza (1999). The VARFIT algorithm 
!   performs a nonlinear minimization of the weighed 
!   squared differences between the experimental
!   variogram and the model. The weighting function
!   is the inverse of the estimation variance.
!   VARFIT uses a non-linear minimisation method
!   that does not require (explicit) initial values for the
!   parameters in order to initialise the minimisation al-
!   gorithm. Jian et al. (1996) report problems with con-
!   vergence if the initial guess is poor.
!   Solution is found with the simplex method of function
!   minimization of Nelder and Mead (1965)
!   This ingenious method is efficient for non-linear mini-
!   mization in a multiparameter space but is still easy to
!   program and only requires evaluations of the objective
!   function. The program stops the iterations whenever
!   one of the two following criteria is reached:
!   1. Convergence is reached
!   2. The maximum number of iterations  is reached.
!   The best fitted semivariogram model is used 
!   to interpolate a regular grid of  data.
!   Code is adapted from the Geostats program
!   written by Luke Spadavecchia, Biosphere Atmosphere 
!   Modelling Group, University of Edinburgh, November 2006.
!   A number of closest points are used to buils 
!   the covariance matrix used to predict value at
!   location where value is unknown. Matrix inversion
!   uses the the Gauss-Jordan method. 
!   An n*2,n work array is assembled, with the array 
!   of interest on the left half, and the identity matrix 
!   on the right half. A check is made to ensure the matrix is 
!   invertable, and the matrix inverse is returned, providing 
!   the matrix is not singular. The matrix is invertable if, 
!   after pivoting and row reduction, the identity matrix 
!   shifts to the left half of the work array.
!   
! References:
!
! Pardo-Iguzquiza, E. VARFIT: a fortran-77 program 
!  for fitting variogram models by weighted least squares. 
!  Computers & Geosciences, 25, 251-261, 1999.
!
! Nelder, J.A., Mead, R., 1965. A simplex method for 
!  function minimization. Computer Journal 7, 308-313.
! 
! Jian, X., Olea, R.A., Yu, Y., 1996. Semivariogram modelling
!  by weighted least squares. Computers & Geosciences 22 (3),
!  387-397.
!
MODULE Kriging


USE DataTypeSizes, ONLY:&
  !Imported parameters:
  float, double, short

USE LogLib, ONLY: &
  !Imported routines:
  Catch

USE GeoLib, ONLY: &
  !Imported variables:
  point1, point2, &
  !Imported routines:
  Distance , DirectionAngle, &
  !Imported definition:
  Coordinate, &
  !Imported operators:
  ASSIGNMENT( = )

USE ObservationalNetworks, ONLY: &
  !Imported definitions:
  ObservationalNetwork, &
  !Imported routines:
   ActualObservations, &
  !Imported definition:
  Observation

USE GridLib, ONLY: &
    !Imported definition:
    grid_real

USE GridOperations, ONLY: &
    !Imported routines:
    GetXY

USE Units, ONLY: &
  !Imported parameters
  pi, degToRad, radToDeg

IMPLICIT NONE


! Global Procedures:
PUBLIC :: Krige

!Public parameters:
INTEGER, PARAMETER, PUBLIC :: SPHERICAL = 1  !!fit spherical semivariogram model
INTEGER, PARAMETER, PUBLIC :: EXPONENTIAL = 2 !!fit exponential semivariogram model
INTEGER, PARAMETER, PUBLIC :: GAUSSIAN = 3 !!fit gaussian semivariogram model
INTEGER, PARAMETER, PUBLIC :: POWER = 4  !!fit power semivariogram model
INTEGER, PARAMETER, PUBLIC :: AUTOFIT = 5 !!automatic fitting among spherical, exponential and gaussian

!Private procedures
PRIVATE :: PairDistance
PRIVATE :: DistMatrix
PRIVATE :: Semivariogram
PRIVATE :: KrigingInitialize
PRIVATE :: Varfit
PRIVATE :: Simplex
PRIVATE :: Valf
PRIVATE :: Focompu
PRIVATE :: Variogr
PRIVATE :: Fconv
PRIVATE :: Separation
PRIVATE :: Sorted
PRIVATE :: Sort
PRIVATE :: Covariance
PRIVATE :: Covariance2D
PRIVATE :: Interpolate


! private declarations:

TYPE pairs
	INTEGER (KIND = short) :: p1, p2 !!id of points pair
	REAL (KIND = float)    :: h !! pair distance 
    REAL (KIND = float)    :: theta !!pair angle
END TYPE pairs
PRIVATE pairs

TYPE site
	INTEGER (KIND = short) :: oid  !! identification code
	REAL (KIND = float)    :: value !!observed value
    REAL (KIND = float)    :: h !! distance (m)
    REAL (KIND = float)    :: theta !!anistropy angle
    TYPE (Coordinate)      :: xyz  !!position
END TYPE site
PRIVATE site

!variables used for fitting variogram
INTEGER (KIND = short), PRIVATE :: ieva !number of evaluations of the objective function
!INTEGER (KIND = short), PUBLIC :: ieva !number of evaluations of the objective function

INTEGER (KIND = short), PRIVATE :: npep !if the nugget effect is estimated  XXXXXX deve essere settato da qualche parte
INTEGER (KIND = short), PRIVATE :: ndir !!number of directions of the variogram

INTEGER (KIND = short), PRIVATE :: nvar ! if nvar =1 it is imposed that variance = experimental variance
INTEGER (KIND = short), PRIVATE :: ipara !! number of parameters to estimate
INTEGER (KIND = short), PRIVATE :: objectiveFunction !!kind of objective function to minimize
INTEGER (KIND = short), PRIVATE :: minpairs = 30!!minimum number of pairs in the lag to be considered valid for semivariogram fitting.


REAL (KIND = float),    PRIVATE :: nugget !!nugget effect (C0)
REAL (KIND = float),    PRIVATE :: range !!range
REAL (KIND = float),    PRIVATE :: partialSill !!partial sill (C1) sill of the variogram


REAL (KIND = float),    PRIVATE :: anisotropyAngle !!anisotropy angle 
REAL (KIND = float),    PRIVATE :: anisotropyRatio !!anisotropy ratio >= 1
REAL (KIND = float),    PRIVATE :: var !! variance
REAL (KIND = float),    PRIVATE :: expVar !! experimental variance
REAL (KIND = float),    PRIVATE :: parRangeMin (5) !!minimum values of parameters
REAL (KIND = float),    PRIVATE :: parRangeMax (5) !!maximum values of parameters
REAL (KIND = float),    PRIVATE :: lagTolerance (4) !! How much the distance between pairs can differ from the exact lag distance and still be included in the lag calculations (m)

REAL (KIND = float),    PRIVATE :: hfina !!value of the objective function at the minimum

INTEGER (KIND = short), ALLOCATABLE, PRIVATE :: lagNumber (:) !!number of lags for each direction
INTEGER (KIND = short), ALLOCATABLE, PRIVATE :: pairNumber (:,:) !!number of pairs for each direction and lag

REAL (KIND = float),    ALLOCATABLE, PRIVATE :: dist (:,:) !!average distance for each direction and lag RIMETTERE PRIVATE
REAL (KIND = float),    ALLOCATABLE, PRIVATE :: empVariogram (:,:) !!experimental (empirical) variogram for each direction and lag  !RIMETTERE PRIVATE
REAL (KIND = float),    ALLOCATABLE, PRIVATE :: modVariogram (:,:) !!modelled (empirical) variogram for each direction and lag  !RIMETTERE PRIVATE

REAL (KIND = float),    ALLOCATABLE, PRIVATE :: dist_pairs (:,:)  !!distance  between pairs 
REAL (KIND = float),    ALLOCATABLE, PRIVATE :: dir_pairs (:,:)  !!direction angle between pairs
REAL (KIND = float),    ALLOCATABLE, PRIVATE :: dir (:) !!angle (radians) between the direction of the variogram and the
                                                    !! x axis, measured from the x axis anti-clockwise

!variables used for kriging interpolation
TYPE(pairs), DIMENSION (:,:), ALLOCATABLE, PRIVATE :: obsdist !!distance matrix
TYPE(site), DIMENSION (:), ALLOCATABLE, PRIVATE :: controlpts !!subset of closest points used for interpolation 
TYPE(pairs), DIMENSION (:,:), ALLOCATABLE :: hobs
REAL (KIND = float), DIMENSION (:), ALLOCATABLE, PRIVATE :: cvest, hest, weights
REAL (KIND = float), DIMENSION (:,:), ALLOCATABLE, PRIVATE :: cvobs
INTEGER (KIND = short), DIMENSION(:), ALLOCATABLE, PRIVATE :: last


!=======         
    CONTAINS
!======= 
! Define procedures contained in this module. 

    
!==============================================================================
!| Description:
!  Interpolate irregularly spaced data using Kriging. The subroutine
!  generates an empirical semivariogram (anisotropy is assumed), 
!  fits the best model among spherical, exponential and gaussian.
!
! References:
!
!  Matheron, G. (1965) Les variables régionalisées et leur estimation: 
!  Une application de la theorie des fonctions aléatoires aux sciences 
!  de la nature. Masson, Paris.
!  
SUBROUTINE Krige &
!
(sitedata, anisotropy, nlags, maxlag, neighbors, varModel, grid, gridvar, &
 points, predictions, predictionsVar)


IMPLICIT NONE

!Arguments with intent (in):
TYPE (ObservationalNetwork), INTENT(IN) :: sitedata
INTEGER (KIND = short), INTENT(IN) :: anisotropy !! if = 1, anisotropy is considered 
INTEGER (KIND = short), INTENT(IN) :: nlags !! the number of discrete distances used for comparing data, if 0 it is set to default value
REAL (KIND = float), INTENT(IN) :: maxlag !! maximum distance to consider for semivariogram assessment (m), if 0 it is automatically computed
INTEGER (KIND = short), INTENT(IN) :: neighbors  !! number of neighbors to consider for interpolation
INTEGER (KIND = short), INTENT(IN) :: varModel !!semivariogram model, autofit for atomatic selecting the best among spherical, exponential, and gaussian
TYPE (Coordinate), OPTIONAL, INTENT(IN) :: points (:) !!list of coordinates for prediction (alternative to regular grid)

!Arguments with intent(inout):
TYPE (grid_real), OPTIONAL, INTENT (INOUT) :: grid  !!interpolated grid
TYPE (grid_real), OPTIONAL, INTENT (INOUT) :: gridvar  !!variance of interpolated grid
REAL ( KIND = float), OPTIONAL, DIMENSION (:), INTENT (INOUT) :: predictions  !interpolated values on not regular spaced points
REAL ( KIND = float), OPTIONAL, DIMENSION (:), INTENT (INOUT) :: predictionsVar !variance of interpolated values on not regular spaced points

!Local declarations
TYPE(ObservationalNetwork) :: active_stations
INTEGER (KIND = short) :: count
INTEGER (KIND = short) :: nclosest !!number of closest points to include in interpolation 
INTEGER (KIND = short) :: i,j  
TYPE (site) :: prediction_point
REAL (KIND = float) :: x, y
TYPE(site), DIMENSION (:), ALLOCATABLE ::  estdist
REAL (KIND = float) :: est !estimated value 
REAL (KIND = float) :: variance
INTEGER (KIND = short) :: outmodel !!semivariogram model selected by varfit
!---------------------------end of declarations--------------------------------

  !first check optional input data
  IF (PRESENT (grid) .AND. PRESENT (points) ) THEN
       CALL Catch ('error', 'Kriging interpolation',  &
          'only grid or points should be set for interpolation, they are mutual exclusive' )
  END IF
  
  
  !create a subset of active stations
  CALL ActualObservations (sitedata, count, active_stations)
  
  !compute pair distance
  CALL PairDistance ( active_stations )
  
  !compute direction angle when anisotropy analysis is required
  IF ( anisotropy == 1 ) THEN
      CALL PairDirection (active_stations )
  END IF
  
  !initialize Kriging 
  CALL KrigingInitialize (anisotropy, nlags, maxlag, count, neighbors, nclosest)
  
  !generate empirical semivariogram
  CALL Semivariogram ( active_stations)  
  
  
  !automatic fitting of empirical semivariogram to the best model
  CALL VarFit (varModel, outModel)
  
  
  !Interpolate
  IF (PRESENT (grid) .OR. PRESENT(points) ) THEN
          !populate distance matrix
          CALL DistMatrix (active_stations)
  
          !set coordinate reference system of prediction point 
          IF (PRESENT (grid)) THEN
              prediction_point % xyz % system = grid % grid_mapping
          ELSE
              prediction_point % xyz % system = points(1) % system
          END IF
  
          !allocate estdist
          ALLOCATE ( estdist ( count ) )
  
  
          !Loop through prediction locations
          IF (PRESENT (grid)) THEN  !interpolate regular spaced points
                  DO i = 1, grid % idim
                      DO j = 1, grid % jdim
                          IF ( grid % mat (i,j) /= grid % nodata) THEN
              
                              !set prediction point
                              CALL GetXY (i, j, grid, x, y)
                              prediction_point % xyz % easting = x
                              prediction_point % xyz % northing = y
              
                              !Build Estimation distance Vector  estdist   
		                       CALL Separation (prediction_point, active_stations, estdist)
               
                              !Sort estdist to find the nearest neighbors of estimation location, and return
		                      !the prediction h vector and h matrix.
                              CALL Sorted (estdist, nclosest) 
              
                              !Calculate est covariance from semivariance model, using the estimate to data
		                      !distance vector
                              CALL Covariance(controlpts%h,controlpts%theta,cvest(1:nclosest), sill = partialsill, range = range, varmodel = outModel)
              
                              !Add extra element for lagrangian minimisation
		                      cvest (nclosest + 1 ) = 1.
              
                              !If control points for this datum are the same as for the last, the next steps
		                      !can be skipped over: Since distances between the observations are unchanged
		                      !the observation covariace matrix and its inverse are also unchanged. 
		                      IF (ANY(last .NE. controlpts%oid)) THEN
			                      !Calculate observation covariance matrix from specified semivariance model, 
			                      !using the observation distance matrix (hobs)
                                  CALL Covariance2d(hobs%h, hobs%theta, cvobs(1:nclosest,1:nclosest), sill = partialsill, range = range, varmodel = outModel)
  
			                      !Add extra row and column for lagrangian minimisation
                                  cvobs ( nclosest + 1, : ) = 1.
			                      cvobs ( :, nclosest + 1 ) = 1.
			                      cvobs ( nclosest + 1, nclosest + 1 ) = 0.
  
			                      !Invert Observation covariance Matrix
                                  CALL Matinv ( cvobs, cvobs )
                  
                              END IF
              
                              !Produce estimate for location
                              CALL Interpolate (cvobs, cvest, controlpts%value, est, variance, nclosest, sill = (partialsill + nugget) )
                              grid % mat (i,j) = est
                      
                              IF (PRESENT(gridvar)) THEN
                                 gridvar % mat (i,j) = variance
                              END IF
              
                  
                              !store last set of control points
		                      last = controlpts % oid
              
                          END IF
                      END DO
                  END DO
  
          ELSE !interpolate not regular spaced points
      
              DO i = 1, SIZE(predictions) 
          
                   prediction_point % xyz % easting = points(i) % easting
                   prediction_point % xyz % northing = points(i) % northing
          
                   !Build Estimation distance Vector  estdist   
		            CALL Separation (prediction_point, active_stations, estdist)
               
                    !Sort estdist to find the nearest neighbors of estimation location, and return
		            !the prediction h vector and h matrix.
                    CALL Sorted (estdist, nclosest) 
              
                    !Calculate est covariance from semivariance model, using the estimate to data
		            !distance vector
                    CALL Covariance(controlpts%h,controlpts%theta,cvest(1:nclosest), sill = partialsill, range = range, varmodel = outModel)
              
                    !Add extra element for lagrangian minimisation
		            cvest (nclosest + 1 ) = 1.
              
                    !If control points for this datum are the same as for the last, the next steps
		            !can be skipped over: Since distances between the observations are unchanged
		            !the observation covariace matrix and its inverse are also unchanged. 
		            IF (ANY(last .NE. controlpts%oid)) THEN
			            !Calculate observation covariance matrix from specified semivariance model, 
			            !using the observation distance matrix (hobs).
                        CALL Covariance2d(hobs%h, hobs%theta, cvobs(1:nclosest,1:nclosest), sill = partialsill, range = range, varmodel = outModel)
  
			            !Add extra row and column for lagrangian minimisation
                        cvobs ( nclosest + 1, : ) = 1.
			            cvobs ( :, nclosest + 1 ) = 1.
			            cvobs ( nclosest + 1, nclosest + 1 ) = 0.
  
			            !Invert Observation covariance Matrix
                        CALL Matinv ( cvobs, cvobs )
                  
                    END IF
              
                    !Produce estimate for location
                    !sill = nugget + partial sill = C0 + C1
		            CALL Interpolate (cvobs, cvest, controlpts%value, est, variance, nclosest, sill = (partialsill + nugget) )
            
                    predictions (i) = est
            
                    IF (PRESENT (predictionsVar)) THEN     
                       predictionsVar (i) = variance
                    END IF
              
                  
                    !store last set of control points
		            last = controlpts%oid
          
              END DO
          END IF
  END IF
  
  !free memory
  DEALLOCATE ( active_stations % obs )
  DEALLOCATE ( dist_pairs )
  IF (ALLOCATED (dir_pairs) ) DEALLOCATE ( dir_pairs )
  DEALLOCATE ( dir )
  DEALLOCATE ( lagNumber ) 
  DEALLOCATE ( dist ) 
  DEALLOCATE ( empVariogram ) 
  DEALLOCATE ( modVariogram)
  DEALLOCATE ( pairNumber ) 
  IF (ALLOCATED (obsdist) )  DEALLOCATE ( obsdist )
  DEALLOCATE ( weights )
  DEALLOCATE ( hest )
  DEALLOCATE ( cvest )
  IF (ALLOCATED ( estdist) )   DEALLOCATE ( estdist )
  DEALLOCATE ( controlpts )
  DEALLOCATE ( hobs )
  DEALLOCATE ( cvobs )
  DEALLOCATE ( last )

  RETURN
END SUBROUTINE Krige


!==============================================================================
!| Description:
!  A subroutine to assemble the global distance. This operation
!  is only carried out once; kriging observations matricies are subsetted from
!  this lookup table to speed up processing.
!
SUBROUTINE DistMatrix &
!
(stations)


IMPLICIT NONE

!Arguments with intent (in):
TYPE (ObservationalNetwork), INTENT(IN) :: stations

!Local declarations
INTEGER (KIND = short) :: i, j, n
TYPE (pairs) ::  comp
  
  
!---------------------------end of declarations--------------------------------
!allocate square matrix where n is the number of available observation points
n = stations % countobs
ALLOCATE(obsdist(n,n))

!set points coordinate reference system
point1 % system = stations % mapping
point2 % system = stations % mapping

DO i=1,n
	DO j=1,n
        !set point1
        point1 % northing = stations % obs (i) % xyz % northing  
        point1 % easting = stations % obs (i) % xyz % easting 
        
        !set point2  
        point2 % northing = stations % obs (j) % xyz % northing  
        point2 % easting = stations % obs (j) % xyz % easting
        
        !prepare pair
        comp % p1 = i
        comp % p2 = j
        comp % h = Distance (point1,point2) 
        
        !populate distance matrix
        obsdist(i,j) = comp
        
	END DO
		
END DO
  
RETURN
END SUBROUTINE DistMatrix



!==============================================================================
!| Description:
!  Compute distance between pairs 
!
SUBROUTINE PairDistance &
!
(stations)


IMPLICIT NONE

!Arguments with intent (in):
TYPE (ObservationalNetwork), INTENT(IN) :: stations

!Local declarations
  INTEGER (KIND = short) :: i, j
  REAL (KIND = float) :: average
  
!---------------------------end of declarations--------------------------------

  
!allocate variables
ALLOCATE ( dist_pairs(stations % countObs,stations % countObs))


! Compute distance among stations and experimental variance
average = 0.
dist_pairs = 0.

DO i = 1, stations % countObs
    average = average + stations % obs (i) % value
    DO j = 1, stations % countObs
        
        IF ( i == j) CYCLE !skip same point pairs, distance is zero
        
        IF ( j < i) CYCLE !this pair is already in the vector; pair(4,2) is the same as pair(2,4)
        
        point1 % northing = stations % obs (i) % xyz % northing  
        point1 % easting = stations % obs (i) % xyz % easting 
          
        point2 % northing = stations % obs (j) % xyz % northing  
        point2 % easting = stations % obs (j) % xyz % easting
                
        dist_pairs(i,j) = Distance (point1,point2)       
        
    END DO
END DO 

average = average / stations % countObs

!compute experimental variance
expVar = 0.
DO i = 1, stations % countObs
    expVar = expVar + ( stations % obs (i) % value - average ) ** 2.
END DO
expVar = expVar / stations % countObs
  
RETURN
END SUBROUTINE PairDistance


!==============================================================================
!| Description:
!  Compute direction angle between pairs (radians) measured from the x axis 
!  anti-clockwise
!
SUBROUTINE PairDirection &
!
(stations)


IMPLICIT NONE

!Arguments with intent (in):
TYPE (ObservationalNetwork), INTENT(IN) :: stations

!Local declarations
  INTEGER (KIND = short) :: i, j
  
!---------------------------end of declarations--------------------------------

  
!allocate variables
ALLOCATE ( dir_pairs (stations % countObs,stations % countObs) )

! Compute direction angle between stations 
dir_pairs = 0.
DO i = 1, stations % countObs
    DO j = 1, stations % countObs
        
        IF ( i == j) CYCLE !skip same point pairs, distance is zero
        
        IF ( j < i) CYCLE !this pair is already in the vector; pair(4,2) is the same as pair(2,4)
        
        point1 % northing = stations % obs (i) % xyz % northing  
        point1 % easting = stations % obs (i) % xyz % easting 
          
        point2 % northing = stations % obs (j) % xyz % northing  
        point2 % easting = stations % obs (j) % xyz % easting
                
        dir_pairs(i,j) = DirectionAngle (point1,point2)   
        
        !direction angle is measured from North clockwise.
        !change convention to angle measured from x- axis (east) anticlockwise.
        IF ( dir_pairs (i,j) <= pi / 2. ) THEN
            dir_pairs (i,j) = pi /2. - dir_pairs (i,j)
        ELSE
            !dir_pairs (i,j) = 3./2.*pi - dir_pairs (i,j)
            dir_pairs (i,j) =   pi + dir_pairs (i,j)
        END IF
       
    END DO
END DO 

RETURN
END SUBROUTINE PairDirection



!==============================================================================
!| Description:
!  generate a Semivariogram from a given sample.
!  The sample point pairs are ordered into even-width bin,
!  separated by the euclidean distance of the point pairs.
!  The Semivariance in the bin is calculated by the Matheron estimator.
!  If number of lags and lag max distance are not given
!  they are automatically computed or set to default value.
!
! References:
!
!  Matheron, G. (1965) Les variables régionalisées et leur estimation: 
!  Une application de la théorie des fonctions aléatoires aux sciences 
!  de la nature. Masson, Paris.
!  
SUBROUTINE Semivariogram &
!
(stations)

IMPLICIT NONE

!Arguments with intent (in):
TYPE (ObservationalNetwork), INTENT(IN) :: stations

!Local declarations
INTEGER (KIND = short) :: i, j, k, m, index
REAL (KIND = float) :: zj, zk !!observation at point j, and k
REAL (KIND = float), ALLOCATABLE :: empVariogramCopy (:,:), distCopy (:,:) 
INTEGER (KIND = short), ALLOCATABLE :: pairNumberCopy (:,:)

!---------------------------end of declarations--------------------------------

!generate empirical semivariogram

DO j = 1, stations % countObs !loop through pairs
   DO k = 1, stations % countObs
      IF ( j == k ) CYCLE !skip same point pairs, distance is zero  
      IF ( k <  j ) CYCLE !this pair is already in the vector; pair(4,2) is the same as pair(2,4)
     
      DO m = 1, ndir
         DO i = 1, lagNumber (m) !loop through lags
                          
                IF ( ndir == 1 ) THEN !isotrophic analysis, direction is not considered
            
                    IF (dist_pairs (j,k) >= dist (m,i) - lagTolerance (m) .AND. &
                        dist_pairs (j,k) < dist (m,i) + lagTolerance (m) ) THEN
                 
                         zj = stations % obs (j) % value
                         zk = stations % obs (k) % value
                 
                         pairNumber (m,i) = pairNumber (m,i) + 1
                         empVariogram (m,i) = empVariogram (m,i) + ( zj - zk )**2.
                
                    END IF
                        
                ELSE !anisotrophy analysis, create bins considering direction
                    IF ( dist_pairs (j,k) >= dist (m,i) - lagTolerance (m) .AND. &
                            dist_pairs (j,k) < dist (m,i) + lagTolerance (m) ) THEN
                        
                        
                       IF (  ( m == 1 .AND. dir_pairs (j,k) < pi / 8. )   .OR. &
                          ( m == 1 .AND. dir_pairs (j,k) >= 15./8. * pi ) .OR. &
                          ( m == 2 .AND. dir_pairs (j,k) >= pi / 8. .AND. dir_pairs (j,k) < 3./8. * pi )    .OR. &
                          ( m == 3 .AND. dir_pairs (j,k) >= 3./8. * pi .AND. dir_pairs (j,k) <= 1./2. * pi ) .OR. &
                          ( m == 3 .AND. dir_pairs (j,k) > 3./2. * pi .AND. dir_pairs (j,k) < 13./8. * pi ) .OR. &
                          ( m == 4 .AND. dir_pairs (j,k) >= 13./8. * pi .AND. dir_pairs (j,k) < 15./8. * pi ) ) THEN
                        
                             zj = stations % obs (j) % value
                             zk = stations % obs (k) % value
                             pairNumber (m,i) = pairNumber (m,i) + 1
                             empVariogram (m,i) = empVariogram (m,i) + ( zj - zk )**2.
                    
                        END IF
                     END IF  
                        
                END IF 
            
         END DO
      ENDDO     
   END DO
END DO


DO m = 1, ndir !loop through directions
  DO i = 1, lagNumber (m) !loop through lags
     empVariogram (m,i) = empVariogram (m,i) / ( 2. * pairNumber (m,i) )
  END DO
END DO

RETURN

END SUBROUTINE Semivariogram


!==============================================================================
!| Description:
!   Fit model to experimental variogram
!
SUBROUTINE Varfit &
!
(varModel, modelselected)

IMPLICIT NONE

!arguments with intent (in):
INTEGER (KIND = short), INTENT(IN) :: varModel

!arguments with intent(out):
INTEGER (KIND = short), INTENT(OUT) :: modelselected !!if automodel is passed as varModel, the optimal model is returned

!local declarations:
REAL (KIND = float) :: ami1, ama1, ami2, ama2, ami3, ama3 
REAL (KIND = float) :: ami4, ama4, ami5, ama5
REAL (KIND = float) :: gmax, gmin, dmin, dmax
REAL (KIND = float) :: fac, fape
INTEGER :: i,j, k
INTEGER (KIND = short) :: nmodels, model
REAL (KIND = float) :: sillmodel (3), rangemodel (3), nuggetmodel (3), of (3), iterations (3)
REAL (KIND = float) :: minimumOF !!minimum value of objective function
!-----------------------------------end of declarations-----------------------!

!!find minimum and maximum values
dmin = MINVAL ( dist, MASK = dist > 0.0 )
dmax = MAXVAL ( dist)
gmin = MINVAL ( empVariogram, MASK = empVariogram > 0.0   )
gmax = MAXVAL ( empVariogram  )

ipara = 2

IF ( ndir > 1 ) ipara = 4
IF ( nvar == 1 ) ipara = ipara - 1
IF ( npep == 1 ) ipara = ipara + 1

!set number of sevivariogram models to fit
IF (varmodel == AUTOFIT) THEN
    nmodels = 2
ELSE
    nmodels = 1
END IF

DO k = 1, nmodels
    
    !reset variables
    ieva = 0 
    nugget = 0.0
    range = 0.0
    partialSill = 0.0
    anisotropyAngle = 0.0
    anisotropyRatio = 1.0
    var = expVar
    
    IF (nmodels == 1) THEN
        model = varmodel
    ELSE
        model = k
    END IF
    
        !     ALCANCE MAYOR
              AMI1=0.1*DMIN
              AMA1=1.5*DMAX
        !     ANGULO DE ANISOTROPIA
              AMI2=0.0
              AMA2=PI
        !     RATIO DE ANISOTROPIA
              AMI3=1.0
              AMA3=20.0
        !     MESETA
              AMI4=0.1*VAR
              AMA4=2.0*VAR
        !     EFECTO DE PEPITA
              AMI5=0.0
              AMA5=VAR
              IF (model == 4) THEN
                FAC=2.0*GMAX/DMAX
                FAPE=2.0*GMIN
              END IF
        !        WRITE (6,*)'PARAMETERS TO ESTIMATE: ',IPARA
                parRangeMin(1)=AMI1
                parRangeMax(1)=AMA1
                IF (IPARA.EQ.2) THEN
                  IF (NPEP.EQ.1) THEN
                    parRangeMin(2)=AMI5
                    parRangeMax(2)=AMA5
                  ELSE
                    parRangeMin(2)=AMI4
                    parRangeMax(2)=AMA4
                    IF (varModel.EQ.4) THEN
                      parRangeMin(2)=0.00001
                      parRangeMax(2)=FAC
                    END IF
                  END IF
                ELSE IF (IPARA.EQ.3) THEN
                  IF (NPEP.EQ.1) THEN
                    parRangeMin(2)=AMI4
                    parRangeMax(2)=AMA4
                    parRangeMin(3)=AMI5
                    parRangeMax(3)=AMA5
                    IF (model.EQ.4) THEN
                      parRangeMin(2)=0.00001
                      parRangeMax(2)=FAC
                      parRangeMin(3)=0.0
                      parRangeMax(3)=FAPE
                    END IF
                  ELSE
                    parRangeMin(2)=AMI2
                    parRangeMax(2)=AMA2
                    parRangeMin(3)=AMI3
                    parRangeMax(3)=AMA3
                  END IF
                ELSE IF (IPARA.EQ.4) THEN
                  parRangeMin(2)=AMI2
                  parRangeMax(2)=AMA2
                  parRangeMin(3)=AMI3
                  parRangeMax(3)=AMA3
                  IF (NPEP.EQ.1) THEN
                    parRangeMin(4)=AMI5
                    parRangeMax(4)=AMA5
                  ELSE
                    parRangeMin(4)=AMI4
                    parRangeMax(4)=AMA4
                    IF (model.EQ.4) THEN
                      parRangeMin(4)=0.00001
                      parRangeMax(4)=FAC
                    END IF
                  END IF
                ELSE
                  parRangeMin(2)=AMI2
                  parRangeMax(2)=AMA2
                  parRangeMin(3)=AMI3
                  parRangeMax(3)=AMA3
                  parRangeMin(4)=AMI4
                  parRangeMax(4)=AMA4
                  parRangeMin(5)=AMI5
                  parRangeMax(5)=AMA5
                  IF (model.EQ.4) THEN
                    parRangeMin(4)=0.00001
                    parRangeMax(4)=FAC
                    parRangeMin(5)=0.0
                    parRangeMax(5)=FAPE
                  END IF
                END IF

        !!     VARIANCE = SILL + NUGGET

              IF (model.EQ.4) THEN
                 parRangeMin(1)=0.00001
                 parRangeMax(1)=1.9
              END IF   

              CALL Simplex (model)
              
              sillmodel (k) = partialSill
              rangemodel (k) = range
              nuggetmodel (k) = nugget
              of (k) = hfina
              iterations (k) = ieva
END DO



IF (nmodels > 1) THEN !set the best model
    minimumOF = 10E6
     DO i = 1, nmodels
          IF ( of (i) < minimumOF ) THEN
              minimumOF = of (i)
              modelselected = i
              partialSill = sillmodel (i)
              range = rangemodel (i)
              nugget = nuggetmodel (i)
              ieva = iterations (i)
              
          END IF
     END DO
ELSE
    modelselected = varModel
END IF

        
IF (nmodels > 1) THEN
    hfina = minimumOF
END IF

RETURN
END SUBROUTINE Varfit



!==============================================================================
!| Description:
!   This subroutine implements the simplex method of
!   function minimization of Nelder and Mead (1965).
!   This ingenious method is efficient for non-linear mini-
!   mization in a multiparameter space but is still easy to
!   program and only requires evaluations of the objective
!   function. The program stops the iterations whenever
!   one of the two following criteria is reached:
!   1. Convergence is reached
!   2. The maximum number of iterations  is reached.
!   
!   Output:
!   hfina: objective function at the minimum
!   parameters of the estimated variogram
!
! References:
!
!   Nelder, J.A., Mead, R., 1965. A simplex method for function
!   minimization. Computer Journal 7, 308-313.
!
SUBROUTINE Simplex &
!
(varModel)


USE Units, ONLY: &
    !imported parameters:
    pi

IMPLICIT NONE

!Arguments with intent(in):
INTEGER (KIND = short), INTENT (IN) :: varModel !!type of variogram: 1 = spherical, 2 = exponential, 3 = gaussian, 4 = power


!local declarations:
INTEGER, PARAMETER :: maxiter = 200 !maximum number of iteration
REAL (KIND = float), PARAMETER :: eps = 0.01 !convergence criterion
REAL (KIND = float), PARAMETER :: refl = 0.5 !reflection factor
REAL (KIND = float), PARAMETER :: expa = 2.0 !expansion factor
REAL (KIND = float), PARAMETER :: cont = 0.5 !contraction factor
REAL (KIND = float) :: pu (7,6)
REAL (KIND = float) :: p1 (6), p2 (6), pf (6)
REAL (KIND = float) :: c (6)
REAL (KIND = float) :: y (7)
REAL (KIND = float) :: val, y1, y2
REAL (KIND = float) :: ymax, ymin, bmax
INTEGER (KIND = short) :: imax, imin
INTEGER (KIND = short) :: i, j

!--------------------------------end of declarations---------------------------

! set initial values
SELECT CASE (ipara)
    CASE (1)
        pu (1,1) = parRangeMin (1)
        pu (2,1) = parRangeMax (1)
    CASE (2) 
        pu (1,1) = parRangeMin (1)
        pu (1,2) = parRangeMin (2)
        pu (2,1) = parRangeMin (1)
        pu (2,2) = parRangeMax (2)
        pu (3,1) = parRangeMax (1)
        pu (3,2) = parRangeMax (2)
    CASE (3)
        pu (1,1) = parRangeMin (1)
        pu (1,2) = parRangeMin (2)
        pu (1,3) = parRangeMin (3)
        pu (2,1) = parRangeMin (1)
        pu (2,2) = parRangeMin (2)
        pu (2,3) = parRangeMax (3)
        pu (3,1) = parRangeMin (1)
        pu (3,2) = parRangeMax (2)
        pu (3,3) = parRangeMin (3)
        pu (4,1) = parRangeMax (1)
        pu (4,2) = parRangeMax (2)
        pu (4,3) = parRangeMax (3)
        IF ( varModel == 4) THEN
            pu (4,1) = parRangeMax (1) / 2.0
            pu (4,2) = parRangeMax (2) / 2.0
            pu (4,3) = parRangeMin (3)
        END IF   
    CASE (4)
         pu (1,1) = parRangeMin (1)
         pu (1,2) = parRangeMin (2)
         pu (1,3) = parRangeMin (3)
         pu (1,4) = parRangeMin (4)
         pu (2,1) = parRangeMin (1)
         pu (2,2) = parRangeMin (2)
         pu (2,3) = parRangeMin (3)
         pu (2,4) = parRangeMax (4)
         pu (3,1) = parRangeMin (1)
         pu (3,2) = parRangeMin (2)
         pu (3,3) = parRangeMax (3)
         pu (3,4) = parRangeMin (4)
         pu (4,1) = parRangeMin (1)
         pu (4,2) = parRangeMax (2)
         pu (4,3) = parRangeMin (3)
         pu (4,4) = parRangeMin (4)
         pu (5,1) = parRangeMax (1)
         pu (5,2) = parRangeMax (2)
         pu (5,3) = parRangeMax (3)
         pu (5,4) = parRangeMax (4)
      CASE DEFAULT 
         pu (1,1) = parRangeMin (1)
         pu (1,2) = parRangeMin (2)
         pu (1,3) = parRangeMin (3)
         pu (1,4) = parRangeMin (4)
         pu (1,5) = parRangeMin (5)
         pu (2,1) = parRangeMin (1)
         pu (2,2) = parRangeMin (2)
         pu (2,3) = parRangeMin (3)
         pu (2,4) = parRangeMin (4)
         pu (2,5) = parRangeMax (5)
         pu (3,1) = parRangeMin (1)
         pu (3,2) = parRangeMin (2)
         pu (3,3) = parRangeMin (3)
         pu (3,4) = parRangeMax (4)
         pu (3,5) = parRangeMin (5)
         pu (4,1) = parRangeMin (1)
         pu (4,2) = parRangeMin (2)
         pu (4,3) = parRangeMax (3)
         pu (4,4) = parRangeMin (4)
         pu (4,5) = parRangeMin (5)
         pu (5,1) = parRangeMin (1)
         pu (5,2) = parRangeMax (2)
         pu (5,3) = parRangeMin (3)
         pu (5,4) = parRangeMin (4)
         pu (5,5) = parRangeMin (5)
         pu (6,1) = parRangeMax (1)
         pu (6,2) = parRangeMax (2)
         pu (6,3) = parRangeMax (3)
         pu (6,4) = parRangeMax (4)
         pu (6,5) = parRangeMin (5)
 END SELECT
DO  i = 1, ipara + 1
  DO j = 1, ipara
     pf (j) = pu (i,j)
  END DO
  y (i) = Valf (pf, varmodel)
END DO  

! simplex algorithm
200 ymax = -1.0E+15
ymin = 1.0E+15
bmax = ymax

DO i = 1, ipara + 1
    
    IF ( y(i) > ymax ) THEN
        ymax = y(i)
        imax = i
    END IF
    
    IF ( y(i) < ymin ) THEN
        ymin = y(i)
        imin = i
    END IF
END DO

DO  i = 1, ipara + 1
    IF (i /= imax) THEN
       IF (y(i) > bmax) bmax = y(i)
    END IF
END DO


!     200 ITERATIONS MAXIMUM

      IF ( ieva > maxiter ) GOTO 300

!    CONTROID

DO  I=1,IPARA
    VAL=0.0
    DO  J=1,IPARA+1
        IF (J.NE.IMAX) VAL=VAL+PU(J,I)
    END DO
    C(I)=VAL/IPARA
END DO

!     P*
  
DO  I=1,IPARA
     P1(I)=(1.0+REFL)*C(I)-REFL*PU(IMAX,I)
END DO
      Y1=VALF(P1, varmodel)
      IF (Y1.GT.YMIN.AND.Y1.LT.BMAX) THEN
         DO  I=1,IPARA
            PU(IMAX,I)=P1(I)
         END DO
         Y(IMAX)=Y1
         IF (FCONV(Y,IPARA).LT.EPS) GOTO 300
         GOTO 200
      END IF
      IF (Y1.LT.YMIN) THEN
         DO 11 I=1,IPARA
            P2(I)=(1.0+EXPA)*P1(I)-EXPA*C(I)
 11      CONTINUE
         Y2=VALF(P2, varmodel)
         IF (Y2.LT.YMIN) THEN
            DO 12 I=1,IPARA
               PU(IMAX,I)=P2(I)
 12         CONTINUE
            Y(IMAX)=Y2
            IF (FCONV(Y,IPARA).LT.EPS) GOTO 300
            GOTO 200
         ELSE
            DO 13 I=1,IPARA
               PU(IMAX,I)=P1(I)
 13         CONTINUE
            Y(IMAX)=Y1
            IF (FCONV(Y,IPARA).LT.EPS) GOTO 300
            GOTO 200
         END IF
      END IF

      IF (Y1.LT.YMAX) THEN
         DO 15 I=1,IPARA
            PU(IMAX,I)=P1(I)
 15      CONTINUE
         Y(IMAX)=Y1
      END IF

      DO 16 I=1,IPARA
         P2(I)=CONT*PU(IMAX,I)+(1.0-CONT)*C(I)
 16   CONTINUE
      Y2=VALF(P2, varmodel)
      IF (Y2.GT.YMAX) THEN
         DO 17 I=1,IPARA+1
            DO 18 J=1,IPARA
               PU(I,J)=(PU(I,J)+PU(IMIN,J))/2.0
 18         CONTINUE
            DO 19 J=1,IPARA
               PF(J)=PU(I,J)
 19         CONTINUE
            Y(I)=VALF(PF, varmodel)
 17      CONTINUE
         IF (FCONV(Y,IPARA).LT.EPS) GOTO 300
         GOTO 200
      ELSE
         DO 1 I=1,IPARA
            PU(IMAX,I)=P2(I)
 1       CONTINUE
         Y(IMAX)=Y2
         IF (FCONV(Y,IPARA).LT.EPS) GOTO 300
         GOTO 200
      END IF

300    IF (IPARA.EQ.1) THEN
          range=PU(IMIN,1)
          anisotropyAngle=0.0
          anisotropyRatio=1.0
          partialSill=VAR
          nugget=0.0
       ELSE IF (IPARA.EQ.2) THEN
          IF (NPEP.EQ.1) THEN
            range=PU(IMIN,1)
            nugget=PU(IMIN,2)
            partialSill=VAR-nugget
            anisotropyAngle=0.0
            anisotropyRatio=1.0
          ELSE
            range=PU(IMIN,1)
            partialSill=PU(IMIN,2)
            nugget=0.0
            anisotropyAngle=0.0
            anisotropyRatio=1.0
            VAR=partialSill
          END IF
       ELSE IF (IPARA.EQ.3) THEN
          IF (NPEP.EQ.1) THEN
            range=PU(IMIN,1)
            partialSill=PU(IMIN,2)
            nugget=PU(IMIN,3)
            VAR=partialSill+nugget
            anisotropyAngle=0.0
            anisotropyRatio=1.0
          ELSE
            range=PU(IMIN,1)
            anisotropyAngle=PU(IMIN,2)
            anisotropyRatio=PU(IMIN,3)
            nugget=0.0
            partialSill=VAR
          END IF
       ELSE IF (IPARA.EQ.4) THEN
          IF (NPEP.EQ.1) THEN
            range=PU(IMIN,1)
            anisotropyAngle=PU(IMIN,2)
            anisotropyRatio=PU(IMIN,3)
            nugget=PU(IMIN,4)
            partialSill=VAR-nugget
          ELSE
            range=PU(IMIN,1)
            anisotropyAngle=PU(IMIN,2)
            anisotropyRatio=PU(IMIN,3)
            partialSill=PU(IMIN,4)
            nugget=0.0
            VAR=partialSill
          END IF
       ELSE
          range=PU(IMIN,1)
          anisotropyAngle=PU(IMIN,2)
          anisotropyRatio=PU(IMIN,3)
          partialSill=PU(IMIN,4)
          nugget=PU(IMIN,5)
          VAR=partialSill+nugget
       END IF
        HFINA=YMIN


RETURN
END SUBROUTINE Simplex



!==============================================================================
!| Description:
!   function called by Simplex
!
REAL FUNCTION Valf &
!
(a, varmodel)


IMPLICIT NONE

!Arguments with intent in:
REAL (KIND = float), INTENT (IN) :: a (:)
INTEGER (KIND = short), INTENT(IN) :: varmodel


!local declarations:
REAL (KIND = float) :: hoo
INTEGER (KIND = short) :: i

!---------------------------end of declarations--------------------------------

DO i = 1, ipara
    IF ( a (i) < parRangeMin (i) .OR. a (i) > parRangeMax (i) ) THEN
        Valf = 1.0E+15
        RETURN
    END IF
END DO
              
ieva = ieva + 1
SELECT CASE (ipara)
  CASE (1)
    range = a(1)
    anisotropyAngle = 0.0
    anisotropyRatio = 1.0
    partialSill = var
    nugget = 0.0
  CASE (2)
    IF (npep == 1) THEN
        range = a(1)
        nugget = a(2)
        partialSill = var - nugget 
        anisotropyAngle = 0.0
        anisotropyRatio = 1.0
    ELSE
        range = a(1)
        partialSill = a(2)
        nugget = 0.0
        anisotropyAngle = 0.0
        anisotropyRatio = 1.0
    END IF
  CASE (3)
    IF (npep == 1) THEN
        range = a(1)
        partialSill = a(2)
        nugget = a(3)
        anisotropyAngle = 0.0
        anisotropyRatio = 1.0
    ELSE
        range = a(1)
        anisotropyAngle = a(2)
        anisotropyRatio = a(3)
        nugget = 0.0
        partialSill = var
    END IF
  CASE (4)
    IF (npep == 1) THEN
        range = a(1)
        anisotropyAngle = a(2)
        anisotropyRatio = a(3)
        nugget = a(4)
        partialSill = var - nugget 
    ELSE
        range = a(1)
        anisotropyAngle = a(2)
        anisotropyRatio = a(3)
        partialSill = a(4)
        nugget = 0.0
    END IF
 CASE DEFAULT
    range = a(1)
    anisotropyAngle = a(2)
    anisotropyRatio = a(3)
    partialSill = a(4)
    nugget = a(5)
END SELECT

CALL Focompu (varmodel, hoo)

Valf = hoo

RETURN
END FUNCTION Valf

!==============================================================================
!| Description:
!   function called by Simplex
!
REAL FUNCTION Fconv &
!
(b, ipara)

IMPLICIT NONE

!Arguments with intent (in):
REAL (KIND = float), INTENT(IN) :: b (:)
INTEGER (KIND = short), INTENT(IN) :: ipara

!local declarations:
REAL (KIND = float) :: xmed, xval
INTEGER (KIND = short) :: i

!--------------------------------------end of declarations---------------------


xmed = 0.0

DO  i = 1, ipara + 1
    xmed = xmed + b (i)
END DO

xmed = xmed / (ipara + 1.0)
xval = 0.0
DO  i = 1, ipara + 1
    xval = xval + (b(i) - xmed )**2
END DO
Fconv = SQRT( xval / (ipara + 1.0) )
       
RETURN
END FUNCTION Fconv




!==============================================================================
!| Description:
!   evaluation of the objective function
!
SUBROUTINE Focompu &
!
(varmodel, hoo)

IMPLICIT NONE

!arguments with intent(in):
INTEGER (KIND = short), INTENT(IN) :: varmodel

!arguments with intent out
REAL (KIND = float), INTENT(OUT) :: hoo

!local declaration:
INTEGER (KIND = short) :: i, j
REAL (KIND = float) :: alpha
REAL (KIND = float) :: h
REAL (KIND = float) :: gam
REAL (KIND = float) :: dif2
REAL (KIND = float) :: wei

!----------------------------------end of declarations-------------------------

hoo = 0.0
DO  i = 1, ndir
    alpha = dir (i)
    DO  j = 1, lagNumber (i)
        h = dist (i,j)
        
        IF (h > 0.0) THEN
            CALL Variogr (varModel, h, alpha, gam)
            dif2 = ( empVariogram(i,j) - gam ) ** 2.
            IF (objectiveFunction == 1) THEN
              wei = 1.0
            ELSE IF (objectiveFunction == 2) THEN
              wei = pairNumber (i,j)
            ELSE IF (objectiveFunction == 3) THEN
              wei = 1.0 / ( gam * gam )
            ELSE IF (objectiveFunction == 4) THEN
              wei = pairNumber (i,j) / ( gam * gam )
            ELSE
              wei = pairNumber(i,j) / ( dist(i,j) * dist(i,j) )
            END IF
            hoo = hoo + wei * dif2
        END IF
        
    END DO
END DO

RETURN
END SUBROUTINE Focompu


!==============================================================================
!| Description:
!   calculation of the variogram value in 2D
!
SUBROUTINE Variogr &
!
(varModel, h, alpha, gam)

IMPLICIT NONE

!arguments with intent(in):
REAL (KIND = float), INTENT(IN) :: alpha
INTEGER (KIND = short), INTENT (IN) :: varmodel

!arguments with intent(out)
REAL (KIND = float), INTENT(OUT) :: gam

!arguments with intent(inout)
REAL (KIND = float), INTENT(INOUT) :: h

!local declaration:
REAL (KIND = float) :: zero
REAL (KIND = float) :: caa, cab, cac, dc2, ds2, x, y, dx, dy, w

!-------------------------------------end of declarations----------------------

zero = 0.000001
gam = 0.0

IF (h < zero) RETURN
gam = nugget

caa = 1.0
cab = 1.0
cac = 0.0

IF ( anisotropyRatio > 1.0 ) THEN
    dc2 = COS(anisotropyAngle)**2
    ds2 = SIN(anisotropyAngle)**2
    caa = dc2 + anisotropyRatio * ds2
    cab = ds2 + anisotropyRatio * dc2
    cac = ( 1.0 - anisotropyRatio ) * SIN(anisotropyAngle) * COS(anisotropyAngle)
END IF

x = h * COS(alpha)
y = h * SIN(alpha)
dx = x * caa + y * cac
dy = x * cac + y * cab
h = SQRT ( dx * dx + dy * dy )

IF ( varModel == 1) THEN !Spherical
    w = 1.5 * h / range - 0.5 * h * h * h / ( range * range * range )
    IF (h > range) w = 1.0
    
ELSE IF ( varModel == 2) THEN !Exponential
     w = 1.0 - EXP ( -3*h / range )
     !w = 1.0 - EXP ( - h / range )
    
ELSE IF ( varModel == 3) THEN !Gaussian
    w = 1.0 - EXP ( -10*h * h / (range * range) )
    
ELSE IF ( varModel == 4) THEN !Power
    w = h ** range
    
END IF   

gam = gam + w * partialSill

RETURN
END SUBROUTINE Variogr



!==============================================================================
!| Description:
!   Initialize variables for kriging
!
SUBROUTINE KrigingInitialize &
!
(anisotropy, nlags, maxlag, count, neighbors, nclosest)
    
IMPLICIT NONE

!Arguments with intent(in):
INTEGER (KIND = short), INTENT(IN) :: anisotropy !! if = 1, anisotropy is considered
INTEGER (KIND = short), INTENT(IN) :: nlags !! the number of discrete distances used for comparing data, if 0 it is set to default value
REAL (KIND = float), INTENT(IN) :: maxlag !! maximum distance to consider for semivariogram assessment (m), if 0 it is automatically computed
INTEGER (KIND = short), INTENT(IN) :: count !!total number of available observations
INTEGER (KIND = short), INTENT(IN) :: neighbors !! number of neighbors to consider for interpolation

!Arguments with intent(out)::
INTEGER (KIND = short), INTENT(OUT) :: nclosest

!local declarations:
INTEGER (KIND = short) :: lagnumbers !!number of lags used in computing semivariogram, default = 15
REAL (KIND = float) :: varcoverage !! variogram coverage, distance to consider for semivariogram assessment, (m), anisotropy case
REAL (KIND = float) :: varcoverageEW !! variogram coverage in East-South direction (0°)
REAL (KIND = float) :: varcoverageNESW !! variogram coverage in NorthEast-SouthWest direction (45°)
REAL (KIND = float) :: varcoverageNS !! variogram coverage in North-Sout direction (90°)
REAL (KIND = float) :: varcoverageNWSE !! variogram coverage in NorthWest-SoutEast direction (315°)
REAL (KIND = float) :: width (4)  !! distance between lags (m)
INTEGER (KIND = short) :: i, j, m, k

!----------------------------------end of declarations-------------------------

!set number of lags and allocate vectors
IF (nlags > 0) THEN
    lagnumbers = nlags
ELSE
    lagnumbers = 15 !default
END IF


IF (anisotropy == 1) THEN 
    ndir = 4
ELSE !only one direction is considered
    ndir = 1   
END IF

!allocate memory for semivariogram
ALLOCATE ( dir (ndir) ) !angle between the direction of the variogram and the x axis
ALLOCATE ( lagNumber (ndir) ) !number of lags for each direction
ALLOCATE ( dist (ndir, lagnumbers) ) !distance for each direction and lag
ALLOCATE ( empVariogram (ndir, lagnumbers) ) !experimental variogram for each direction and lag
ALLOCATE ( modVariogram (ndir, lagnumbers) ) !modelled variogram for each direction and lag
ALLOCATE ( pairNumber (ndir, lagnumbers) ) !number of pairs for each direction and lag

!set distance for semivariogram computation
IF (maxlag > 0.) THEN
    varcoverage = maxlag
ELSE !set to maximum distance between points
    IF (anisotropy == 0) THEN
         varcoverage = 2. ** 0.5 * MAXVAL (dist_pairs) / 2.  !sqrt(2) *1/2 Maximum point distance
    ELSE !search for maximum distance along  directions
       varcoverageEW   = 0.
       varcoverageNESW = 0.
       varcoverageNS   = 0.
       varcoverageNWSE = 0.
       DO j = 1, count !loop through pairs
           DO k = 1, count
               !EW direction
               IF ( dir_pairs (j,k) < pi / 8. .OR. dir_pairs (j,k) >= 15./8. * pi ) THEN 
                   IF ( dist_pairs (j,k) > varcoverageEW ) varcoverageEW = dist_pairs (j,k)
               END IF
               
               !NE-SW direction
               IF ( dir_pairs (j,k) >= pi / 8. .AND. dir_pairs (j,k) < 3./8. * pi  ) THEN 
                   IF ( dist_pairs (j,k) > varcoverageNESW ) varcoverageNESW = dist_pairs (j,k)
               END IF
               
               !N-S direction
               IF ( dir_pairs (j,k) >= 3./8. * pi .AND. dir_pairs (j,k) <= 1./2. * pi .OR. &
                    dir_pairs (j,k) > 3./2. * pi .AND. dir_pairs (j,k) < 13./8. * pi  ) THEN 
                   IF ( dist_pairs (j,k) > varcoverageNS ) varcoverageNS = dist_pairs (j,k)
               END IF
                    
               !NW-SE direction
               IF ( dir_pairs (j,k) >= 13./8. * pi .AND. dir_pairs (j,k) < 15./8. * pi  ) THEN 
                   IF ( dist_pairs (j,k) > varcoverageNWSE ) varcoverageNWSE = dist_pairs (j,k)
               END IF     
               
           END DO
       END DO
                          
    END IF
END IF

varcoverageEW = 2. ** 0.5 * varcoverageEW / 2.
varcoverageNESW = 2. ** 0.5 * varcoverageNESW / 2.
varcoverageNS = 2. ** 0.5 * varcoverageNS / 2.
varcoverageNWSE = 2. ** 0.5 * varcoverageNWSE / 2.



!set distance between lags (m). Assume even spaced lags
IF (anisotropy == 0) THEN
    width = varcoverage / lagnumbers
ELSE
    width (1) = varcoverageEW / lagnumbers
    width (2) = varcoverageNESW / lagnumbers
    width (3) = varcoverageNS / lagnumbers
    width (4) = varcoverageNWSE / lagnumbers
END IF

!populate lags vectors
DO i = 1, ndir
    DO j = 1, lagnumbers
       dist (i,j) = width (i) * j
    END DO
END DO

!set tolerance
IF (anisotropy == 0) THEN
  lagTolerance = width (1) / 2. !by default tolerance is half lag width
ELSE
    DO i = 1, ndir
        lagTolerance (i) = width (i) / 2.
    END DO
END IF
!initialize
DO i = 1, ndir !number of lags is the same for each direction
  lagNumber (i) = lagnumbers
END DO

IF (anisotropy) THEN
    !set 4 directions: E-W (0°), NE-SW (45°), N-S (90°), NW-SE (315°)
    dir (1) = 0. !E-W
    dir (2) = 45. * degToRad ! NE-SW
    dir (3) = 90. * degToRad ! N-S
    dir (4) = 315. * degToRad ! NW-SE
ELSE
    !direction is not used
    dir (1) = 0. 
END IF

pairNumber = 0
empVariogram = 0.

ieva = 0
nugget = 0.0
range = 0.0
partialSill = 0.0
anisotropyAngle = 0.0
anisotropyRatio = 1.0
objectiveFunction = 4 !set objective function to minimize

npep = 1 !1 = nugget is estimated
nvar = 0 !1 means variance = experimental variance

!find the dimension of matrixes for interpolation
IF (neighbors > 0) THEN
    IF ( neighbors > count ) THEN !too many neighbors respect to available observations
         nclosest = count !limit dimension to available observations number
    ELSE
         nclosest = neighbors
    END IF
ELSE !neighbors = 0 => include all observations
    nclosest = count
END IF

!Allocate memory for interpolation
ALLOCATE ( weights ( nclosest + 1 ) )
ALLOCATE ( hest ( nclosest ) )
ALLOCATE ( cvest ( nclosest + 1 ) )
ALLOCATE ( controlpts ( nclosest ) )
ALLOCATE ( hobs ( nclosest,nclosest ) )
ALLOCATE ( cvobs( nclosest + 1, nclosest + 1 ) )
ALLOCATE ( last (nclosest) )


RETURN
END SUBROUTINE KrigingInitialize



!==============================================================================
!| Description:
!   Subroutine to find the distances between prediction point and
!	observations. Also returns angles if anisotropy present
!
SUBROUTINE Separation &
!!
(prediction, observations, estdist)

IMPLICIT NONE

!Arguments with intent(in):
TYPE (site), INTENT(IN) :: prediction
TYPE (ObservationalNetwork), INTENT(IN) :: observations

!Arguments with intent(out):
TYPE( site), DIMENSION(:), INTENT(out) :: estdist

!local declarations:
INTEGER (KIND = short) :: i

!----------------------------------------end of declarations-------------------

DO i = 1, observations % countObs
  estdist (i) % h = Distance (prediction % xyz, observations % obs (i) % xyz)
  estdist (i) % oid = i
  estdist (i) % value = observations % obs (i) % value
  estdist (i) % xyz % easting = observations % obs (i) % xyz % easting
  estdist (i) % xyz % northing = observations % obs (i) % xyz % northing
  !Return the angle between points if anisotropy is present 
  IF ( anisotropyAngle > 0.) THEN
     estdist (i) % theta = pi / 2. - DirectionAngle (prediction % xyz, observations % obs (i) % xyz)
     !estdist (i) % theta = ATAN ( (observations % obs (i) % xyz % northing - prediction % xyz % northing) /  &
     ! (observations % obs (i) % xyz % easting - prediction % xyz % easting)  )  
  ELSE
     estdist (i) % theta = 0.
  END IF
  
END DO

RETURN
END SUBROUTINE Separation


!==============================================================================
!| Description:
! A subroutine to retrieve the nearest neighbors of the estimation location
! referred to as control points. The observations separation matrix is
! returned (as a precursor to producing the observations covariance matrix)
! along with the vector of nearest neighbbors.
!
SUBROUTINE Sorted &
!
( estdist, neighbors )
	
IMPLICIT NONE

!Arguments with intent(in):
INTEGER (KIND = short), INTENT(IN) :: neighbors

!Arguments with intent(inout):
TYPE(site),DIMENSION(:),INTENT(inout) :: estdist
	
!Local variable declarations
INTEGER :: i,j,p1,p2

!------------------------------------end of declarations-----------------------
	
!Sort estdist by h (distance) to find the nearest neighbors of the prediction location
CALL Sort (estdist,0)

!Select control points for kriging: the n nearest neighbors
controlpts = estdist(1:neighbors)

!Construct observations distance matrix (hobs) from the control points
DO i=1,neighbors
	DO j=1,neighbors
		p1=controlpts(i)%oid
		p2=controlpts(j)%oid
		hobs(i,j)=obsdist(p1,p2)
	END DO
END DO

END SUBROUTINE Sorted


!==============================================================================
!| Description:
! Subroutine to sort a vector of points using the Heap-sort algorithm. 
! Data are sorted by value if flag =1, or by separation distance if flag = 0.
! Actually, only a pointer is sorted, as this is more efficient. Subroutine
! Adapted from Numerical Recipes in Fortran 90: Press, Teukolsky, Vetterling 
! and Flannery (1996) pg. 1171 
!
SUBROUTINE Sort &
!
(input, flag)
	
IMPLICIT NONE
	
!Arguments with intent(in):
INTEGER,INTENT(in) :: flag

!Arguments with intent(inout) 
TYPE(site), DIMENSION(:), INTENT(inout) :: input

	
!Local variable declarations
INTEGER :: i,n
TYPE(site), DIMENSION(:), POINTER :: work
TYPE(site) :: dummy
!----------------------------------end of declarations-------------------------
	
n=SIZE(input)
ALLOCATE(work(n))
work=input

DO i=n/2,1,-1
	!Loop down the left range of the sift-down: The element to be sifted
	!is decremented to n/2 to 1 during "Hiring" in heap creation
	CALL sift_down(i,n,flag)
END DO

DO i=n,2,-1
	!Loop down the right range of the sift-down: Decremented from n-1 to
	!1 during the "Retirement and promotion" stage of heap creation
		
	!Clear space at the top of the array and retire the top of the heap into it
	dummy=work(1)
	work(1)=work(i)
	work(i)=dummy

	CALL sift_down(1,i-1,flag)
END DO

input=work

DEALLOCATE (work)

CONTAINS

SUBROUTINE sift_down(l,r,flag)
	IMPLICIT NONE
	!Dummy argument declaration
	INTEGER,INTENT(in) :: l,r,flag
	
	!Local variable declarations
	INTEGER :: j,old
	Type(site) :: a
	
	!Carry out sift-down on element array(l) to maintain heap structure
		
	!Get element to sift
	a=work(l)
	old=l
	j=l+l
		
	SELECT CASE(flag)

	CASE(0)
		!If flag = 0, sort by h
		!Do while j<=r
		DO
			IF(j>r) EXIT
			IF(j<r) THEN
				!Compare to the better underling
				IF(work(j)%h<work(j+1)%h) j=j+1
			END IF

			!If found a's level, terminate the sift-down, else demote and continue
			IF(a%h >= work(j)%h) EXIT
			work(old) = work(j)
			old=j
			j=j+j
		END DO
		
	CASE(1)
		!If flag = 1, sort by value
		!Do while j<=r
		DO
			IF(j>r) EXIT
			IF(j<r) THEN
				!Compare to the better underling
				IF(work(j)%value<work(j+1)%value) j=j+1
			END IF

			!If found a's level, terminate the sift-down, else demote and continue
			IF(a%value >= work(j)%value) EXIT
			work(old) = work(j)
			old=j
			j=j+j
		END DO
		
	END SELECT

	!Put into its slot
	work(old)=a
END SUBROUTINE sift_down

END SUBROUTINE Sort




!==============================================================================
!| Description:
! Subroutine to calculate a covariance vector from a semivariogram model
! and a vector of separation distances.
!
SUBROUTINE Covariance &
!
(dist, theta, cov, sill, range, varmodel)

IMPLICIT NONE

!Arguments with intent(in)
REAL (KIND = float), INTENT(in) ::  sill  !! partial sill (total sill - nugget) from automatic fitting
REAL (KIND = float), INTENT(in) :: range  !! range from automatic fitting
REAL (KIND = float), DIMENSION(:), INTENT(IN) :: dist !!separation distance vector
REAL (KIND = float), DIMENSION(:), INTENT(IN) :: theta !!anisotropy angle ??
INTEGER (KIND = short), INTENT(in) :: varmodel  !!semivariogram model: 1 = spherical, 2 = exponential, 3 = gaussian, 4 = power

!Arguments with intent(out):
REAL (KIND = float), DIMENSION(:), INTENT(OUT) :: cov !!covariance vector


!Local variable declarations
REAL (KIND = float), DIMENSION(SIZE(dist)) :: h_prime
INTEGER (KIND = short) :: i

!------------------------------end of declarations-----------------------------

!h_prime = dist ! da correggere con anisotropia quando sarà implementata
    
!Check for anisotropy
IF (anisotropyAngle == 0.) THEN
	h_prime = dist
ELSE
	!Transform distance vector by applying anisotropy correction factor:
	!The minor axis of variation reaches sill before major axis of variation;
	!this is achieved by adding extra distance to the point separations 
	!to 'pull back' the range towards the origin. Separation distances 
	!are reprojected into a transformed coordinate system h'. 
	!First theta is rotated to the major axis of semivariance lies on the horizontal.
	!The anisotropy correction factor is calculated by multiplying the SIN of the 
	!angle between the major axis of variation and the point separation angle by the 
	!eccentricity of the anisotropy ellipsoid (major semiaxis/minor semiaxis). 
	!The transformed coordinate system h' is then the product of the separation 
	!and the correction factor.
    
	!h_prime = dist + dist * ABS( SIN( theta - rotation(i) ) * (theta3(i)/theta2(i)) )
    h_prime = dist + dist * ABS( SIN( theta - anisotropyAngle ) * anisotropyRatio )
END IF
        
!covariance = nugget + partialsill - semivariogram(h)
!since semivariogram(h) = nugget + partialsill * f(h,range) => covariance = partialsill * ( 1- f(h,range) )
SELECT CASE(varmodel)
    CASE (1) !Spherical
		WHERE(h_prime < range)
        cov = sill * (1. - ( 1.5 * (h_prime / range) - 0.5 * (h_prime / range)**3. ) )
		ELSEWHERE
        cov = 0.
        END WHERE
             
    CASE (2) !Exponential
        WHERE(h_prime < range)
        !cov = sill * (1. - ( 1. - EXP (- h_prime / range) ) )
        cov = sill * (1. - ( 1. - EXP (- 3 * h_prime / range) ) )
		ELSEWHERE
        cov = 0.
        END WHERE
             
    CASE (3) !Gaussian: MUST have a nugget effect to function without instability
        WHERE(h_prime < range)
        !cov = sill * (1. - ( 1. - EXP (- h_prime * h_prime / (range * range) ) ) ) 
        cov = sill * (1. - ( 1. - EXP (- 10 * h_prime * h_prime / (range * range) ) ) ) 
		ELSEWHERE
        cov = 0.
        END WHERE
             
    CASE (4) !Power
        WHERE(h_prime < range)
        cov = sill * (1. - (h_prime / range) ) 
		ELSEWHERE
        cov = 0.
        END WHERE

END SELECT 

RETURN

END SUBROUTINE Covariance


!==============================================================================
!| Description:
!  Subroutine to calculate a covariance matrix of observations 
!  from a semivariogram model and a vector of separation distances.
!
SUBROUTINE Covariance2D &
!
(dist, theta, cov, sill, range, varmodel)

IMPLICIT NONE

!Arguments with intent(in)
REAL (KIND = float), INTENT(in) ::  sill  !! partial sill (total sill - nugget) from automatic fitting
REAL (KIND = float), INTENT(in) :: range  !! range from automatic fitting
REAL (KIND = float), DIMENSION(:,:), INTENT(IN) :: dist !!separation distance vector
REAL (KIND = float), DIMENSION(:,:), INTENT(IN) :: theta !!anisotropy angle ??
INTEGER (KIND = short), INTENT(in) :: varmodel  !!semivariogram model: 1 = spherical, 2 = exponential, 3 = gaussian, 4 = power

!Arguments with intent(out):
REAL (KIND = float), DIMENSION(:,:), INTENT(OUT) :: cov  !!covariance matrix

!Local variable declarations
REAL (KIND = float), DIMENSION(SIZE(dist,1),SIZE(dist,2)) :: h_prime 
INTEGER (KIND = short) :: i

!---------------------------------end of declarations--------------------------

!h_prime = dist ! da correggere con anisotropia quando sarà implementata

	
!Check for anisotropy
IF (anisotropyAngle == 0.) THEN
	h_prime = dist
ELSE
	!Transform distance vector by applying anisotropy correction factor
 	h_prime = dist + dist * ABS( SIN( theta - anisotropyAngle ) * anisotropyRatio )
END IF

!covariance = nugget + partialsill - semivariogram(h)
!since semivariogram(h) = nugget + partialsill * f(h,range) => covariance = partialsill * ( 1- f(h,range) )
SELECT CASE(varmodel)
	CASE (1) !Spherical
		WHERE(h_prime < range)
        cov = sill * (1. - ( 1.5 * (h_prime / range) - 0.5 * (h_prime / range)**3. ) )
		ELSEWHERE
        cov = 0.
        END WHERE
             
    CASE (2) !Exponential
        WHERE(h_prime < range)
        !cov = sill * (1. - ( 1. - EXP (- h_prime / range) ) )
        cov = sill * (1. - ( 1. - EXP (- 3*  h_prime / range) ) )
		ELSEWHERE
        cov = 0.
        END WHERE
             
    CASE (3) !Gaussian: MUST have a nugget effect to function without instability
        WHERE(h_prime < range)
        !cov = sill * (1. - ( 1. - EXP (- h_prime * h_prime / (range * range) ) ) ) 
        cov = sill * (1. - ( 1. - EXP (- 10 * h_prime * h_prime / (range * range) ) ) ) 
		ELSEWHERE
        cov = 0.
        END WHERE
             
    CASE (4) !Power
        WHERE(h_prime < range)
        cov = sill * (1. - (h_prime / range) ) 
		ELSEWHERE
        cov = 0.
        END WHERE

END SELECT 

RETURN	
END SUBROUTINE Covariance2D



!==============================================================================
!| A generic subroutine to invert a square 2D matrix by pivoting 
! ("Gauss-Jordan" method). An n*2,n work array is assembled, with the array 
! of interest on the left half, and the identity matrix on the right half. 
! A check is made to ensure the matrix is invertable, and the matrix inverse 
! is returned, providing the matrix is not singular. The matrix
! is invertable if, after pivoting and row reduction, the identity matrix 
! shifts to the left half of the work array. Initially the code was written 
! in integer form to avoid fractions in the work array, but numbers rapidly 
! become inconcevably large. The implemented solution therefore works on 
! fractional pivot rows, with pivots factored to leading ones. 
! Double precision arrays have been used to maintain numerical stability.
!
! Copyright (C) 2006  Luke Spadavecchia
! This program is free software; you can redistribute it and/or modify
! it under the terms of the GNU General Public License as published by
! the Free Software Foundation; either version 2 of the License, or
! (at your option) any later version.
! This program is distributed in the hope that it will be useful,
! but WITHOUT ANY WARRANTY; without even the implied warranty of
! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
! GNU General Public License for more details.
! You should have received a copy of the GNU General Public License along
! with this program; if not, write to the Free Software Foundation, Inc.,
! 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
!
SUBROUTINE Matinv &
!
(input, output)
	
IMPLICIT NONE

!Arguments with intent(in):
REAL (KIND = float), DIMENSION(:,:), INTENT(in) :: input

!Arguments with intent(out):
REAL (KIND = float), DIMENSION(:,:), INTENT(out) :: output

!Local variable declarations
INTEGER (KIND = short) :: i, row ,n, column
INTEGER (KIND = short), DIMENSION ( SIZE (input,1), SIZE(input,2) ) :: identity
REAL (KIND = double) :: a, b, pivot
REAL (KIND = double), DIMENSION ( SIZE(input,1)*2, SIZE(input,2) ) :: work
!---------------------------end of declarations--------------------------------

!Get size of input
n=SIZE(input,1)

!Build identity matrix
identity=0
	
!Make diagonals = 1
DO i=1,n
	identity(i,i)=1
END DO

!Build work array:	
!Input array on the left half. 
work(1:n,:)=input

!Identity matrix on the right half
work(n+1:n*2,:) = identity

!Step through the work array rows
DO row=1,n
	!Step through row j's columns
	DO i=1,n*2
		!Find first non-zero element of row and record it as the pivot
		IF (work(i,row) .NE. 0) THEN
			pivot = work(i,row)
			column = i
			EXIT
		END IF
	END DO

	!Divide pivot row by pivot to rescale for leading 1
	work(:,row)=work(:,row)/pivot

	!Loop over rows to 'Clear' the pivot column using the pivot value
	DO i=1,n
		!Skip over if i is the pivot row
		IF (i == row) CYCLE

		!Get clearance row scaling factor
		b = work(column,i)
				
		!Replace row with the following:
		!Difference between clearance row and (b * pivot row)
		work(:,i) = work(:,i) - b*work(:,row)
	END DO 
END DO

!Validate that the inversion is successful, by checking left side of work array
!is equal to the identity matrix.
IF (ANY(work(1:n,:) .NE. identity)) THEN
	PRINT '("Inversion not possible: Matrix is degenerate!"/)'
	PAUSE
	STOP
END IF

!Output the inverse of input array (right hand side of work array).
output = work(n+1:2*n,:) 

RETURN
END SUBROUTINE Matinv



!==============================================================================
!| A subroutine to predict the data value of an unsampled location, 
! using geostatistical methods. Interpolation is carried out using 
! the 'Ordinary Kriging' method. 
! 
SUBROUTINE Interpolate &
!
(cvobs, cvest, controlpts, est, var, neighbors, sill)
	
IMPLICIT NONE

!Arguments with intent(in):
INTEGER (KIND = short),              INTENT(IN) :: neighbors
REAL (KIND = float),                 INTENT(IN) :: sill
REAL (KIND = float), DIMENSION(:),   INTENT(IN) :: cvest
REAL (KIND = float), DIMENSION(:,:), INTENT(IN) :: cvobs
REAL (KIND = float), DIMENSION(:),   INTENT(IN) :: controlpts

!arguments with intent(out):
REAL (KIND = float), INTENT(OUT) :: est !!estimated value
REAL (KIND = float), INTENT(OUT) :: var !!variance of estimated value

!Local variable declarations
INTEGER (KIND = short) :: n
!REAL,DIMENSION(neighbors+1) :: musv
REAL (KIND = float) :: ck
!--------------------------end of declarations---------------------------------

n = SIZE(controlpts)

!Derive Weights
weights = MATMUL(cvobs,cvest)
		
!Check weights sum to 1 (n=1 th element is the lagrange parameter)
ck = SUM(weights(1:neighbors))
IF (NINT(ck) .NE. 1) THEN
    CALL Catch ('warning', 'Kriging interpolation',  &
          'Kriging weights do not sum to 1' )
END IF
			 
!Estimate datum value sum(weights*observations)
est = SUM ( weights(1:neighbors) * controlpts )

!Estimate the local mean: derive new set of weights from the observtions covariance
!matrix and an estimateion covariance vector which is equal to zero. This filters
!the residual component of the estimate, returning the local mean.
!musv=0
!musv(neighbors+1)=1
!musv=MATMUL(cvobs,musv)
!localmu=SUM(musv(1:neighbors)*controlpts)

!Derive Estimation Variance: ssum(weights*semivariances) + lagrangian
var = sill - SUM(weights(1:n)*cvest(1:n)) + weights(n+1)

IF ( var < 0. ) THEN
    CALL Catch ('warning', 'Kriging interpolation',  &
          'Negative variance produced! Check validity &
            of semivariogram model' )
END IF

RETURN
END SUBROUTINE Interpolate
    
END MODULE Kriging